/**
 * libpsd - Photoshop file formats (*.psd) decode library
 * Copyright (C) 2004-2007 Graphest Software.
 *
 * libpsd is the legal property of its developers, whose names are too numerous
 * to list here.  Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Library General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * $Id: descriptor.c, created by Patrick in 2006.06.18, libpsd@graphest.com Exp $
 */

#include <math.h>
#include "libpsd.h"
#include "psd_system.h"
#include "psd_descriptor.h"
#include "psd_stream.h"
#include "psd_color.h"
#include "psd_math.h"


typedef psd_double psd_matrix[4][4];

static psd_matrix psd_basis_matrix =
{
  { -0.5,  1.5, -1.5,  0.5 },
  {  1.0, -2.5,  2.0, -0.5 },
  { -0.5,  0.0,  0.5,  0.0 },
  {  0.0,  1.0,  0.0,  0.0 },
};

/*  this can be adjusted to give a finer or coarser curve  */
#define PSD_CURVES_SUBDIVIDE  512

psd_static void psd_stream_get_object_list(psd_context * context);


psd_static void psd_stream_get_unicode_name(psd_context * context)
{
    psd_int length;
    
    length = psd_stream_get_int(context) * 2;
    psd_stream_get_null(context, length);
}

psd_static void psd_stream_get_object_id(psd_context * context)
{
    psd_int length;
    
    length = psd_stream_get_int(context);
    if(length == 0)
        psd_stream_get_int(context);
    else
        psd_stream_get_null(context, length);
}

// 'obj ' = Reference
psd_static void psd_stream_get_object_reference(psd_context * context)
{
    psd_uint type;
    psd_int number_items;

    // Number of items
    number_items = psd_stream_get_int(context);

    while(number_items --)
    {
        // OSType key for type to use
        type = psd_stream_get_int(context);

        switch(type)
        {
            // 'prop' = Property
            case 'prop':
                // Unicode string: name from classID
                psd_stream_get_unicode_name(context);
                // classID: 4 bytes (length), followed either by string or (if length is zero) 4-
                // byte classID    
                psd_stream_get_object_id(context);
                // KeyID: 4 bytes (length), followed either by string or (if length is zero) 4-
                // byte keyID
                psd_stream_get_object_id(context);
                break;
                
            // 'Clss' = Class
            case 'Clss':
                // Unicode string: name from classID
                psd_stream_get_unicode_name(context);
                // classID: 4 bytes (length), followed either by string or (if length is zero) 4-
                // byte classID                
                psd_stream_get_object_id(context);
                break;
                
            // 'Enmr' = Enumerated Reference
            case 'Enmr':
                // Unicode string: name from classID
                psd_stream_get_unicode_name(context);
                // classID: 4 bytes (length), followed either by string or (if length is zero) 4-
                // byte classID                
                psd_stream_get_object_id(context);
                // TypeID: 4 bytes (length), followed either by string or (if length is zero) 4-
                // byte typeID
                psd_stream_get_object_id(context);
                // enum: 4 bytes (length), followed either by string or (if length is zero) 4-
                // byte enum
                psd_stream_get_object_id(context);
                break;
                
            // 'rele' = Offset
            case 'rele':
                // Unicode string: name from classID
                psd_stream_get_unicode_name(context);
                // classID: 4 bytes (length), followed either by string or (if length is zero) 4-
                // byte classID                
                psd_stream_get_object_id(context);
                // Value of the offset
                psd_stream_get_int(context);
                break;
                
            // 'Idnt' = Identifier
            case 'Idnt':
                psd_stream_get_int(context);
                break;
                
            // 'indx' = Index
            case 'indx':
                psd_stream_get_int(context);
                break;
            
            // 'name' =Name
            case 'name':
                psd_stream_get_unicode_name(context);
                break;

            default:
                psd_assert(0);
                break;
        }
    }
}

// 'doub' = Double
psd_static void psd_stream_get_object_double(psd_context * context)
{
    // Actual value (double)
    psd_stream_get_null(context, 8);
}

// 'UntF' = Unit psd_float
psd_static void psd_stream_get_object_unit_float(psd_context * context)
{
    // Units the following value is in
    psd_stream_get_int(context);

    // Actual value (double)
    psd_stream_get_null(context, 8);
}

// 'TEXT' = String
psd_static void psd_stream_get_object_string(psd_context * context)
{
    psd_stream_get_unicode_name(context);
}

// 'enum'= Enumerated
psd_static void psd_stream_get_object_enumerated(psd_context * context)
{
    // TypeID: 4 bytes (length), followed either by string or (if length is zero) 4-
    // byte typeID
    psd_stream_get_object_id(context);
    // enum: 4 bytes (length), followed either by string or (if length is zero) 4-
    // byte enum
    psd_stream_get_object_id(context);
}

// 'long' = Integer
psd_static void psd_stream_get_object_integer(psd_context * context)
{
    // Value
    psd_stream_get_int(context);
}

// 'bool' = Boolean
psd_static void psd_stream_get_object_boolean(psd_context * context)
{
    // Boolean value
    psd_stream_get_char(context);
}

// 'type' or GlbC'= Class
psd_static void psd_stream_get_object_class(psd_context * context)
{
    // Unicode string: name from classID
    psd_stream_get_unicode_name(context);
    // classID: 4 bytes (length), followed either by string or (if length is zero) 4-
    // byte classID                
    psd_stream_get_object_id(context);
}

// 'alis' = Alias
psd_static void psd_stream_get_object_alias(psd_context * context)
{
    psd_int length;
    
    // Length of data to follow
    length = psd_stream_get_int(context);

    // FSSpec for Macintosh or a handle to a string to the full path on Windows
    psd_stream_get_null(context, length);
}

// 'Objc' = Descriptor
psd_static void psd_stream_get_object_descriptor(psd_context * context)
{
    psd_uint type;
    psd_int length, number_items;
    
    // Unicode string: name from classID
    psd_stream_get_unicode_name(context);

    // classID: 4 bytes (length), followed either by string or (if length is zero) 4-
    // byte classID                
    psd_stream_get_object_id(context);

    // Number of items in descriptor
    number_items = psd_stream_get_int(context);

    while(number_items --)
    {
        // Key: 4 bytes ( length) followed either by string or (if length is zero) 4-byte
        // key
        length = psd_stream_get_int(context);
        if(length == 0)
            psd_stream_get_int(context);
        else
            psd_stream_get_null(context, length);
        // Type: OSType key
        type = psd_stream_get_int(context);

        switch(type)
        {
            case 'obj ':
                psd_stream_get_object_reference(context);
                break;
            case 'Objc':
            case 'GlbO':    // GlobalObject same as Descriptor
                psd_stream_get_object_descriptor(context);
                break;
            case 'VlLs':
                psd_stream_get_object_list(context);
                break;
            case 'doub':
                psd_stream_get_object_double(context);
                break;
            case 'UntF':
                psd_stream_get_object_unit_float(context);
                break;
            case 'TEXT':
                psd_stream_get_object_string(context);
                break;
            case 'enum':
                psd_stream_get_object_enumerated(context);
                break;
            case 'long':
                psd_stream_get_object_integer(context);
                break;
            case 'bool':
                psd_stream_get_object_boolean(context);
                break;
            case 'type':
            case 'GlbC':    // 'type' or 'GlbC'= Class
                psd_stream_get_object_class(context);
                break;
            case 'alis':
                psd_stream_get_object_alias(context);
                break;
            default:
                psd_assert(0);
                break;
        }
    }
}

// 'VlLs' = List
psd_static void psd_stream_get_object_list(psd_context * context)
{
    psd_uint type;
    psd_int number_items;

    // Number of items in the list
    number_items = psd_stream_get_int(context);

    while(number_items --)
    {
        // OSType key for type to use.
        type = psd_stream_get_int(context);
        
        switch(type)
        {
            case 'obj ':
                psd_stream_get_object_reference(context);
                break;
            case 'Objc':
            case 'GlbO':    // GlobalObject same as Descriptor
                psd_stream_get_object_descriptor(context);
                break;
            case 'VlLs':
                psd_stream_get_object_list(context);
                break;
            case 'doub':
                psd_stream_get_object_double(context);
                break;
            case 'UntF':
                psd_stream_get_object_unit_float(context);
                break;
            case 'TEXT':
                psd_stream_get_object_string(context);
                break;
            case 'enum':
                psd_stream_get_object_enumerated(context);
                break;
            case 'long':
                psd_stream_get_object_integer(context);
                break;
            case 'bool':
                psd_stream_get_object_boolean(context);
                break;
            case 'type':
            case 'GlbC':    // 'type' or 'GlbC'= Class
                psd_stream_get_object_class(context);
                break;
            case 'alis':
                psd_stream_get_object_alias(context);
                break;
            default:
                psd_assert(0);
                break;
        }
    }
}

void psd_stream_get_object_null(psd_uint type, psd_context * context)
{
    switch(type)
    {
        case 'obj ':
            psd_stream_get_object_reference(context);
            break;
        case 'Objc':
        case 'GlbO':    // GlobalObject same as Descriptor
            psd_stream_get_object_descriptor(context);
            break;
        case 'VlLs':
            psd_stream_get_object_list(context);
            break;
        case 'doub':
            psd_stream_get_object_double(context);
            break;
        case 'UntF':
            psd_stream_get_object_unit_float(context);
            break;
        case 'TEXT':
            psd_stream_get_object_string(context);
            break;
        case 'enum':
            psd_stream_get_object_enumerated(context);
            break;
        case 'long':
            psd_stream_get_object_integer(context);
            break;
        case 'bool':
            psd_stream_get_object_boolean(context);
            break;
        case 'type':
        case 'GlbC':    // 'type' or 'GlbC'= Class
            psd_stream_get_object_class(context);
            break;
        case 'alis':
            psd_stream_get_object_alias(context);
            break;
        default:
            psd_assert(0);
            break;
    }
}

psd_argb_color psd_stream_get_object_color(psd_context * context)
{
    psd_uint key;
    psd_int length, number_colors;
    psd_int red, green, blue;
    
    // Unicode string: name from classID
    length = psd_stream_get_int(context) * 2;
    psd_stream_get_null(context, length);

    /***************************************************************************/
    // classID: 4 bytes (length), followed either by string or (if length is zero) 4-
    // byte classID
    length = psd_stream_get_int(context);
    psd_assert(length == 0);
    key = psd_stream_get_int(context);
    // rgb color
    psd_assert(key == 'RGBC');
    
    // Number of color component
    number_colors = psd_stream_get_int(context);
    // must be 3
    psd_assert(number_colors == 3);

    // Key: 4 bytes ( length) followed either by string or (if length is zero) 4-byte key
    length = psd_stream_get_int(context);
    psd_assert(length == 0);
    key = psd_stream_get_int(context);
    // red component
    psd_assert(key == 'Rd  ');
    // Type: OSType key
    key = psd_stream_get_int(context);
    // Double
    psd_assert(key == 'doub');
    // Actual value (double)
    red = (psd_int)psd_stream_get_double(context);

    // Key: 4 bytes ( length) followed either by string or (if length is zero) 4-byte key
    length = psd_stream_get_int(context);
    psd_assert(length == 0);
    key = psd_stream_get_int(context);
    // green component
    psd_assert(key == 'Grn ');
    // Type: OSType key
    key = psd_stream_get_int(context);
    // Double
    psd_assert(key == 'doub');
    // Actual value (double)
    green = (psd_int)psd_stream_get_double(context);

    // Key: 4 bytes ( length) followed either by string or (if length is zero) 4-byte key
    length = psd_stream_get_int(context);
    psd_assert(length == 0);
    key = psd_stream_get_int(context);
    // blue component
    psd_assert(key == 'Bl  ');
    // Type: OSType key
    key = psd_stream_get_int(context);
    // Double
    psd_assert(key == 'doub');
    // Actual value (double)
    blue = (psd_int)psd_stream_get_double(context);

    return psd_rgb_to_color((unsigned char)(red), (unsigned char)(green), (unsigned char)(blue));
}

psd_static void psd_contour_compose(psd_matrix a, psd_matrix b, psd_matrix ab)
{
    psd_int i, j;
    
    for (i = 0; i < 4; i++)
    {
        for (j = 0; j < 4; j++)
        {
            ab[i][j] = (a[i][0] * b[0][j] +
                a[i][1] * b[1][j] +
                a[i][2] * b[2][j] +
                a[i][3] * b[3][j]);
        }
    }
}

psd_static void psd_contour_plot(psd_uchar * table, 
    psd_uchar * input_value, psd_uchar * output_value, psd_bool * corner, 
    psd_int p1, psd_int p2, psd_int p3, psd_int p4)
{
    psd_matrix geometry;
    psd_matrix tmp1, tmp2;
    psd_matrix deltas;
    psd_double  x, dx, dx2, dx3;
    psd_double  y, dy, dy2, dy3;
    psd_double  d, d2, d3;
    psd_int     lastx, lasty;
    psd_int     newx, newy;
    psd_int     i;

    if(corner[p2] == psd_true)
        p1 = p2;
    if(corner[p3] == psd_true)
        p4 = p3;

    /* construct the geometry matrix from the segment */
    for (i = 0; i < 4; i++)
    {
        geometry[i][2] = 0;
        geometry[i][3] = 0;
    }

    geometry[0][0] = input_value[p1];
    geometry[1][0] = input_value[p2];
    geometry[2][0] = input_value[p3];
    geometry[3][0] = input_value[p4];
    geometry[0][1] = output_value[p1];
    geometry[1][1] = output_value[p2];
    geometry[2][1] = output_value[p3];
    geometry[3][1] = output_value[p4];

    /* subdivide the curve */
    d = 1.0 / PSD_CURVES_SUBDIVIDE;
    d2 = d * d;
    d3 = d * d * d;

    /* construct a temporary matrix for determining the forward
    * differencing deltas
    */
    tmp2[0][0] = 0;       tmp2[0][1] = 0;       tmp2[0][2] = 0;  tmp2[0][3] = 1;
    tmp2[1][0] = d3;      tmp2[1][1] = d2;      tmp2[1][2] = d;  tmp2[1][3] = 0;
    tmp2[2][0] = 6 * d3;  tmp2[2][1] = 2 * d2;  tmp2[2][2] = 0;  tmp2[2][3] = 0;
    tmp2[3][0] = 6 * d3;  tmp2[3][1] = 0;       tmp2[3][2] = 0;  tmp2[3][3] = 0;

    /* compose the basis and geometry matrices */
    psd_contour_compose (psd_basis_matrix, geometry, tmp1);

    /* compose the above results to get the deltas matrix */
    psd_contour_compose (tmp2, tmp1, deltas);

    /* extract the x deltas */
    x   = deltas[0][0];
    dx  = deltas[1][0];
    dx2 = deltas[2][0];
    dx3 = deltas[3][0];

    /* extract the y deltas */
    y   = deltas[0][1];
    dy  = deltas[1][1];
    dy2 = deltas[2][1];
    dy3 = deltas[3][1];

    lastx = (psd_int)PSD_CONSTRAIN(x, 0, 255);
    lasty = (psd_int)PSD_CONSTRAIN(y, 0, 255);

    table[lastx] = lasty;

    /* loop over the curve */
    for (i = 0; i < PSD_CURVES_SUBDIVIDE; i++)
    {
        /* increment the x values */
        x += dx;
        dx += dx2;
        dx2 += dx3;

        /* increment the y values */
        y += dy;
        dy += dy2;
        dy2 += dy3;

        newx = (psd_int)(x + 0.5);
        newx = PSD_CONSTRAIN(newx, 0, 255);
        newy = (psd_int)(y + 0.5);
        newy = PSD_CONSTRAIN(newy, 0, 255);

        /* if this point is different than the last one...then draw it */
        if ((lastx != newx) || (lasty != newy))
            table[newx] = newy;

        lastx = newx;
        lasty = newy;
    }
}

psd_static void psd_contour_calculate_table(psd_uchar * lookup_table, psd_int number_point,
    psd_uchar * input_value, psd_uchar * output_value, psd_bool * corner)
{
    psd_int i, x, y;
    psd_int p1, p2, p3, p4;

    /*  Initialize boundary curve points */
    for(i = 0; i < input_value[0]; i ++)
        lookup_table[i] = output_value[0];
    for(i = input_value[number_point - 1]; i < 256; i ++)
        lookup_table[i] = output_value[number_point - 1];

    for(i = 0; i < number_point - 1; i ++)
    {
        p1 = (i == 0) ? i : i - 1;
        p2 = i;
        p3 = i + 1;
        p4 = (i == (number_point - 2)) ? number_point - 1 : i + 2;

        psd_contour_plot(lookup_table, input_value, output_value, corner, p1, p2, p3, p4);
    }

    /* ensure that the control points are used exactly */
    for (i = 0; i < number_point; i++)
    {
        x = input_value[i];
        y = output_value[i];
        lookup_table[x] = y;
    }
}

void psd_stream_get_object_contour(psd_uchar * lookup_table, psd_context * context)
{
    psd_uchar input_value[256], output_value[256];
    psd_bool corner[256];
    psd_uint key, type;
    psd_int i, length, number_item, number_point, number_param;

    // Unicode string: name from classID
    length = psd_stream_get_int(context) * 2;
    psd_stream_get_null(context, length);

    /***************************************************************************/
    // classID: 4 bytes (length), followed either by string or (if length is zero) 4-
    // byte classID
    length = psd_stream_get_int(context);
    psd_assert(length == 0);
    key = psd_stream_get_int(context);
    // shape contour
    psd_assert(key == 'ShpC');

    // Number of items in descriptor
    number_item = psd_stream_get_int(context);

    if(number_item == 2)
    {
        length = psd_stream_get_int(context);
        psd_assert(length == 0);
        key = psd_stream_get_int(context);
        // name of contour
        psd_assert(key == 'Nm  ');
        type = psd_stream_get_int(context);
        psd_assert(type == 'TEXT');

        length = psd_stream_get_int(context) * 2;
        psd_stream_get_null(context, length);
    }

    length = psd_stream_get_int(context);
    psd_assert(length == 0);
    key = psd_stream_get_int(context);
    // curve
    psd_assert(key == 'Crv ');
    type = psd_stream_get_int(context);
    // list
    psd_assert(type == 'VlLs');

    // Number of items in the list
    number_point = psd_stream_get_int(context);
    for(i = 0; i < number_point; i ++)
    {
        type = psd_stream_get_int(context);
        psd_assert(type == 'Objc');

        length = psd_stream_get_int(context) * 2;
        psd_stream_get_null(context, length);

        length = psd_stream_get_int(context);
        psd_assert(length == 0);
        key = psd_stream_get_int(context);
        // curve point
        psd_assert(key == 'CrPt');

        // set default value
        input_value[i] = output_value[i] = 0;
        corner[i] = psd_false;

        number_param = psd_stream_get_int(context);
        while(number_param --)
        {
            length = psd_stream_get_int(context);
            psd_assert(length == 0);
            key = psd_stream_get_int(context);
            type = psd_stream_get_int(context);

            switch(key)
            {
                // horizontal coordinate
                case 'Hrzn':
                    psd_assert(type == 'doub');
                    input_value[i] = (psd_int)psd_stream_get_double(context);
                    break;
                // vertical coordinate
                case 'Vrtc':
                    psd_assert(type == 'doub');
                    output_value[i] = (psd_int)psd_stream_get_double(context);
                    break;
                // point corner
                case 'Cnty':
                    psd_assert(type == 'bool');
                    corner[i] = psd_true - psd_stream_get_bool(context);
                    break;
                default:
                    psd_assert(0);
                    psd_stream_get_object_null(type, context);
                    break;
            }
        }
    }

    psd_contour_calculate_table(lookup_table, number_point, 
        input_value, output_value, corner);
}

psd_blend_mode psd_stream_get_object_blend_mode(psd_context * context)
{
    psd_blend_mode blend_mode = psd_blend_mode_normal;
    psd_int length;
    psd_uint tag;
    psd_uchar keychar[256];

    length = psd_stream_get_int(context);
    if(length == 0)
    {
        tag = psd_stream_get_int(context);
        switch(tag)
        {
            case 'Nrml':
                blend_mode = psd_blend_mode_normal;
                break;
            case 'Dslv':
                blend_mode = psd_blend_mode_dissolve;
                break;
            case 'Drkn':
                blend_mode = psd_blend_mode_darken;
                break;
            case 'Mltp':
                blend_mode = psd_blend_mode_multiply;
                break;
            case 'CBrn':
                blend_mode = psd_blend_mode_color_burn;
                break;
            case 'Lghn':
                blend_mode = psd_blend_mode_lighten;
                break;
            case 'Scrn':
                blend_mode = psd_blend_mode_screen;
                break;
            case 'CDdg':
                blend_mode = psd_blend_mode_color_dodge;
                break;
            case 'Ovrl':
                blend_mode = psd_blend_mode_overlay;
                break;
            case 'SftL':
                blend_mode = psd_blend_mode_soft_light;
                break;
            case 'HrdL':
                blend_mode = psd_blend_mode_hard_light;
                break;
            case 'Dfrn':
                blend_mode = psd_blend_mode_difference;
                break;
            case 'Xclu':
                blend_mode = psd_blend_mode_exclusion;
                break;
            case 'H   ':
                blend_mode = psd_blend_mode_hue;
                break;
            case 'Strt':
                blend_mode = psd_blend_mode_saturation;
                break;
            case 'Clr ':
                blend_mode = psd_blend_mode_color;
                break;
            case 'Lmns':
                blend_mode = psd_blend_mode_luminosity;
                break;
            default:
                psd_assert(0);
                break;
        }
    }
    else
    {
        psd_stream_get(context, keychar, length);
        keychar[length] = 0;
        if(strcmp(keychar, "linearBurn") == 0)
            blend_mode = psd_blend_mode_linear_burn;
        else if(strcmp(keychar, "linearDodge") == 0)
            blend_mode = psd_blend_mode_linear_dodge;
        else if(strcmp(keychar, "vividLight") == 0)
            blend_mode = psd_blend_mode_vivid_light;
        else if(strcmp(keychar, "linearLight") == 0)
            blend_mode = psd_blend_mode_linear_light;
        else if(strcmp(keychar, "pinLight") == 0)
            blend_mode = psd_blend_mode_pin_light;
        else if(strcmp(keychar, "hardMix") == 0)
            blend_mode = psd_blend_mode_hard_mix;
        else
            psd_assert(0);
    }

    return blend_mode;
}

psd_technique_type psd_stream_get_object_technique(psd_context * context)
{
    psd_technique_type technique_type = psd_technique_softer;
    psd_uint tag;
    psd_int length;

    length = psd_stream_get_int(context);
    if(length == 0)
    {
        tag = psd_stream_get_int(context);
        switch(tag)
        {
            case 'SfBL':
                technique_type = psd_technique_softer;
                break;
            case 'PrBL':
                technique_type = psd_technique_precise;
                break;
            case 'Slmt':
                technique_type = psd_technique_slope_limit;
                break;
            default:
                psd_assert(0);
                break;
        }
    }

    return technique_type;
}

psd_gradient_style psd_stream_get_object_gradient_style(psd_context * context)
{
    psd_gradient_style style = psd_gradient_style_linear;
    psd_int length;
    psd_uint tag;
    
    // enum: 4 bytes (length), followed either by string or (if length is zero) 4-
    // byte enum
    length = psd_stream_get_int(context);
    psd_assert(length == 0);
    tag = psd_stream_get_int(context);
    switch(tag)
    {
        case 'Lnr ':
            style = psd_gradient_style_linear;
            break;
        case 'Rdl ':
            style = psd_gradient_style_radial;
            break;
        case 'Angl':
            style = psd_gradient_style_angle;
            break;
        case 'Rflc':
            style = psd_gradient_style_reflected;
            break;
        case 'Dmnd':
            style = psd_gradient_style_diamond;
            break;
        default:
            psd_assert(0);
            break;
    }

    return style;
}

void psd_stream_get_object_gradient_color(psd_gradient_color * gradient_color, psd_context * context)
{
    psd_int i, length, number_items, number_lists, number_data;
    psd_uint key, rootkey, type;
    psd_uchar keychar[256];
    
    // Unicode string: name from classID
    length = psd_stream_get_int(context) * 2;
    psd_stream_get_null(context, length);

    // classID: 4 bytes (length), followed either by string or (if length is zero) 4-
    // byte classID
    length = psd_stream_get_int(context);
    psd_assert(length == 0);
    key = psd_stream_get_int(context);
    // Gradient
    psd_assert(key == 'Grdn');

    // Number of items in descriptor
    number_items = psd_stream_get_int(context);

    while(number_items --)
    {
        length = psd_stream_get_int(context);
        psd_assert(length == 0);
        if(length == 0)
            rootkey = psd_stream_get_int(context);
        else
        {
            rootkey = 0;
            psd_stream_get(context, keychar, length);
            keychar[length] = 0;
        }
        // Type: OSType key
        type = psd_stream_get_int(context);

        switch(rootkey)
        {
            case 'Nm  ':    // name
                // String
                psd_assert(type == 'TEXT');

                // String value as Unicode string
                gradient_color->name_length = psd_stream_get_int(context);
                gradient_color->name = (psd_ushort *)psd_malloc(2 * gradient_color->name_length);
                if(gradient_color->name == NULL)
                    return;
                memset(gradient_color->name, 0, 2 * gradient_color->name_length);

                psd_stream_get(context, (psd_uchar *)gradient_color->name, 2 * gradient_color->name_length);
                break;

            case 'GrdF':    // gradient custom
                // Enumerated
                psd_assert(type == 'enum');

                // TypeID: 4 bytes (length), followed either by string or (if length is zero) 4-
                // byte typeID
                length = psd_stream_get_int(context);
                psd_assert(length == 0);
                key = psd_stream_get_int(context);
                // Gradient Type
                psd_assert(key == 'GrdF');

                // enum: 4 bytes (length), followed either by string or (if length is zero) 4-
                // byte enum
                length = psd_stream_get_int(context);
                psd_assert(length == 0);
                key = psd_stream_get_int(context);
                psd_assert(key == 'CstS');
                break;

            case 'Intr':    // smoothness
                // double
                psd_assert(type == 'doub');

                // for Interpolation ?
                gradient_color->smoothness = (psd_int)psd_stream_get_double(context);
                break;

            case 'Clrs':    // color stop
                // value list
                psd_assert(type == 'VlLs');

                number_lists = psd_stream_get_int(context);
                // Number of color stops
                gradient_color->number_color_stops = number_lists;
                gradient_color->color_stop = (psd_gradient_color_stop *)psd_malloc(
                    gradient_color->number_color_stops * sizeof(psd_gradient_color_stop));
                if(gradient_color->color_stop == NULL)
                    return;
                memset(gradient_color->color_stop, 0, gradient_color->number_color_stops * sizeof(psd_gradient_color_stop));

                /***************************************************************************/
                for(i = 0; i < gradient_color->number_color_stops; i ++)
                {
                    /***************************************************************************/
                    // Type: OSType key
                    type = psd_stream_get_int(context);
                    // Descriptor
                    psd_assert(type == 'Objc');

                    // Unicode string: name from classID
                    length = psd_stream_get_int(context) * 2;
                    psd_stream_get_null(context, length);

                    // classID: 4 bytes (length), followed either by string or (if length is zero) 4-
                    // byte classID
                    length = psd_stream_get_int(context);
                    psd_assert(length == 0);
                    key = psd_stream_get_int(context);
                    // color stop
                    psd_assert(key == 'Clrt');

                    // Number of items in descriptor
                    number_data = psd_stream_get_int(context);
                    // maybe be 4, include color, color stop type, location and midpoint
                    psd_assert(number_data == 4);
                    
                    length = psd_stream_get_int(context);
                    psd_assert(length == 0);
                    key = psd_stream_get_int(context);
                    psd_assert(key == 'Clr ');

                    // Type: OSType key
                    type = psd_stream_get_int(context);
                    // Descriptor
                    psd_assert(type == 'Objc');

                    gradient_color->color_stop[i].actual_color = psd_stream_get_object_color(context);

                    /***************************************************************************/
                    // classID: 4 bytes (length), followed either by string or (if length is zero) 4-
                    // byte classID
                    length = psd_stream_get_int(context);
                    psd_assert(length == 0);
                    key = psd_stream_get_int(context);
                    // Type
                    psd_assert(key == 'Type');
                    
                    // Type: OSType key
                    type = psd_stream_get_int(context);
                    // Enumerated
                    psd_assert(type == 'enum');

                    // TypeID: 4 bytes (length), followed either by string or (if length is zero) 4-
                    // byte typeID
                    length = psd_stream_get_int(context);
                    psd_assert(length == 0);
                    key = psd_stream_get_int(context);
                    // Gradient Type
                    //psd_assert(key == 'GrdF');

                    // enum: 4 bytes (length), followed either by string or (if length is zero) 4-
                    // byte enum
                    length = psd_stream_get_int(context);
                    psd_assert(length == 0);
                    key = psd_stream_get_int(context);
                    switch(key)
                    {
                        case 'FrgC':
                            gradient_color->color_stop[i].color_stop_type = psd_color_stop_type_foreground_color;
                            break;
                        case 'BckC':
                            gradient_color->color_stop[i].color_stop_type = psd_color_stop_type_background_Color;
                            break;
                        case 'UsrS':
                            gradient_color->color_stop[i].color_stop_type = psd_color_stop_type_user_stop;
                            break;
                        default:
                            psd_assert(0);
                            break;
                    }

                    /***************************************************************************/
                    // classID: 4 bytes (length), followed either by string or (if length is zero) 4-
                    // byte classID
                    length = psd_stream_get_int(context);
                    psd_assert(length == 0);
                    key = psd_stream_get_int(context);
                    // Location
                    psd_assert(key == 'Lctn');
                    
                    // Type: OSType key
                    type = psd_stream_get_int(context);
                    // Integer
                    psd_assert(type == 'long');

                    gradient_color->color_stop[i].location = psd_stream_get_int(context);

                    /***************************************************************************/
                    // classID: 4 bytes (length), followed either by string or (if length is zero) 4-
                    // byte classID
                    length = psd_stream_get_int(context);
                    psd_assert(length == 0);
                    key = psd_stream_get_int(context);
                    // midpoint
                    psd_assert(key == 'Mdpn');
                    
                    // Type: OSType key
                    type = psd_stream_get_int(context);
                    // Integer
                    psd_assert(type == 'long');

                    gradient_color->color_stop[i].midpoint = psd_stream_get_int(context);
                }
                break;

            case 'Trns':    // tranparency stop
                // value list
                psd_assert(type == 'VlLs');

                number_lists = psd_stream_get_int(context);
                // Number of color stops
                gradient_color->number_transparency_stops = number_lists;
                gradient_color->transparency_stop = (psd_gradient_transparency_stop *)psd_malloc(
                    gradient_color->number_transparency_stops * sizeof(psd_gradient_transparency_stop));
                if(gradient_color->transparency_stop == NULL)
                    return;
                memset(gradient_color->transparency_stop, 0, gradient_color->number_transparency_stops * 
                    sizeof(psd_gradient_transparency_stop));

                /***************************************************************************/
                for(i = 0; i < gradient_color->number_transparency_stops; i ++)
                {
                    /***************************************************************************/
                    // Type: OSType key
                    type = psd_stream_get_int(context);
                    // Descriptor
                    psd_assert(type == 'Objc');

                    // Unicode string: name from classID
                    length = psd_stream_get_int(context) * 2;
                    psd_stream_get_null(context, length);

                    // classID: 4 bytes (length), followed either by string or (if length is zero) 4-
                    // byte classID
                    length = psd_stream_get_int(context);
                    psd_assert(length == 0);
                    key = psd_stream_get_int(context);
                    // transparency stop
                    psd_assert(key == 'TrnS');

                    // Number of items in descriptor
                    number_data = psd_stream_get_int(context);
                    // maybe be 3, include opacity, location and midpoint
                    psd_assert(number_data == 3);

                    /***************************************************************************/
                    // classID: 4 bytes (length), followed either by string or (if length is zero) 4-
                    // byte classID
                    length = psd_stream_get_int(context);
                    psd_assert(length == 0);
                    key = psd_stream_get_int(context);
                    // opacity
                    psd_assert(key == 'Opct');
                    
                    // Type: OSType key
                    type = psd_stream_get_int(context);
                    // Unit psd_float
                    psd_assert(type == 'UntF');

                    // '#Prc' = percent:
                    key = psd_stream_get_int(context);
                    psd_assert(key == '#Prc');

                    // Actual value (double)
                    gradient_color->transparency_stop[i].opacity = (psd_int)psd_stream_get_double(context);

                    /***************************************************************************/
                    // classID: 4 bytes (length), followed either by string or (if length is zero) 4-
                    // byte classID
                    length = psd_stream_get_int(context);
                    psd_assert(length == 0);
                    key = psd_stream_get_int(context);
                    // Location
                    psd_assert(key == 'Lctn');
                    
                    // Type: OSType key
                    type = psd_stream_get_int(context);
                    // Integer
                    psd_assert(type == 'long');

                    gradient_color->transparency_stop[i].location = psd_stream_get_int(context);

                    /***************************************************************************/
                    // classID: 4 bytes (length), followed either by string or (if length is zero) 4-
                    // byte classID
                    length = psd_stream_get_int(context);
                    psd_assert(length == 0);
                    key = psd_stream_get_int(context);
                    // midpoint
                    psd_assert(key == 'Mdpn');
                    
                    // Type: OSType key
                    type = psd_stream_get_int(context);
                    // Integer
                    psd_assert(type == 'long');

                    gradient_color->transparency_stop[i].midpoint = psd_stream_get_int(context);
                }
                break;
                
            default:
                psd_assert(0);
                psd_stream_get_object_null(type, context);
                break;
        }
    }
}

void psd_stream_get_object_pattern_info(psd_pattern_info * pattern_info, psd_context * context)
{
    psd_int i, length, number_items, identifier_length;
    psd_uint key, rootkey, type;
    psd_uchar keychar[256];
    
    // Unicode string: name from classID
    length = psd_stream_get_int(context) * 2;
    psd_stream_get_null(context, length);

    // classID: 4 bytes (length), followed either by string or (if length is zero) 4-
    // byte classID
    length = psd_stream_get_int(context);
    psd_assert(length == 0);
    key = psd_stream_get_int(context);
    // pattern
    psd_assert(key == 'Ptrn');

    // Number of items in descriptor
    number_items = psd_stream_get_int(context);

    while(number_items --)
    {
        length = psd_stream_get_int(context);
        psd_assert(length == 0);
        if(length == 0)
            rootkey = psd_stream_get_int(context);
        else
        {
            rootkey = 0;
            psd_stream_get(context, keychar, length);
            keychar[length] = 0;
        }
        // Type: OSType key
        type = psd_stream_get_int(context);

        switch(rootkey)
        {
            case 'Nm  ':
                // String
                psd_assert(type == 'TEXT');
                // String value as Unicode string
                pattern_info->name_length = psd_stream_get_int(context);
                pattern_info->name = (psd_ushort *)psd_malloc(2 * pattern_info->name_length);
                if(pattern_info->name == NULL)
                    return;
                memset(pattern_info->name, 0, 2 * pattern_info->name_length);

                psd_stream_get(context, (psd_uchar *)pattern_info->name, 2 * pattern_info->name_length);
                break;
                
            case 'Idnt':
                // String
                psd_assert(type == 'TEXT');
                // String value as Unicode string
                identifier_length = psd_stream_get_int(context);
                for(i = 0; i < identifier_length; i ++)
                    pattern_info->identifier[i] = psd_stream_get_short(context) & 0x00FF;
                break;

            default:
                psd_assert(0);
                psd_stream_get_object_null(type, context);
                break;
        }
    }
}

void psd_stream_get_object_point(psd_int * horz, psd_int * vert, psd_context * context)
{
    psd_int length, number_item;
    psd_uint key, rootkey, type;
    
    // Unicode string: name from classID
    length = psd_stream_get_int(context) * 2;
    psd_stream_get_null(context, length);

    // classID: 4 bytes (length), followed either by string or (if length is zero) 4-
    // byte classID
    length = psd_stream_get_int(context);
    psd_assert(length == 0);
    key = psd_stream_get_int(context);
    // point
    psd_assert(key == 'Pnt ');

    // Number of items in descriptor
    number_item = psd_stream_get_int(context);

    while(number_item --)
    {
        length = psd_stream_get_int(context);
        psd_assert(length == 0);
        rootkey = psd_stream_get_int(context);
        // Type: OSType key
        type = psd_stream_get_int(context);

        switch(rootkey)
        {
            // horizontal
            case 'Hrzn':
                if(type == 'UntF')
                {
                    // '#Prc' = percent:
                    key = psd_stream_get_int(context);
                    psd_assert(key == '#Prc');
                }
                //else if(type == 'doub')
                // Actual value (double)
                *horz = (psd_int)psd_stream_get_double(context);
                break;

            // vertical
            case 'Vrtc':
                if(type == 'UntF')
                {
                    // '#Prc' = percent:
                    key = psd_stream_get_int(context);
                    psd_assert(key == '#Prc');
                }
                //else if(type == 'doub')
                // Actual value (double)
                *vert = (psd_int)psd_stream_get_double(context);
                break;

            default:
                psd_assert(0);
                psd_stream_get_object_null(type, context);
                break;
        }
    }
}

